"""Glulx opcodes, operands and related fun"""

#	Glulx opcodes
op_nop = 0x00

op_add = 0x10
op_sub = 0x11
op_mul = 0x12
op_div = 0x13
op_mod = 0x14
op_neg = 0x15
op_bitand = 0x18
op_bitor = 0x19
op_bitxor = 0x1A
op_bitnot = 0x1B
op_shiftl = 0x1C
op_sshiftr = 0x1D
op_ushiftr = 0x1E

op_jump = 0x20
op_jz  = 0x22
op_jnz = 0x23
op_jeq = 0x24
op_jne = 0x25
op_jlt = 0x26
op_jge = 0x27
op_jgt = 0x28
op_jle = 0x29
op_jltu = 0x2A
op_jgeu = 0x2B
op_jgtu = 0x2C
op_jleu = 0x2D

op_call = 0x30
op_return = 0x31
op_catch = 0x32
op_throw = 0x33
op_tailcall  = 0x34

op_copy = 0x40
op_copys = 0x41
op_copyb = 0x42
op_sexs = 0x44
op_sexb = 0x45
op_aload = 0x48
op_aloads = 0x49
op_aloadb = 0x4A
op_aloadbit  = 0x4B
op_astore = 0x4C
op_astores = 0x4D
op_astoreb = 0x4E
op_astorebit = 0x4F

op_stkcount  = 0x50
op_stkpeek = 0x51
op_stkswap = 0x52
op_stkroll = 0x53
op_stkcopy = 0x54

op_streamchar = 0x70
op_streamnum = 0x71
op_streamstr = 0x72
op_streamunichar = 0x73

op_gestalt = 0x100
op_debugtrap = 0x101
op_getmemsize = 0x102
op_setmemsize = 0x103
op_jumpabs = 0x104

op_random = 0x110
op_setrandom = 0x111

op_quit = 0x120
op_verify = 0x121
op_restart = 0x122
op_save = 0x123
op_restore = 0x124
op_saveundo  = 0x125
op_restoreundo = 0x126
op_protect = 0x127

op_glk = 0x130

op_getstringtbl = 0x140
op_setstringtbl = 0x141
op_getiosys  = 0x148
op_setiosys  = 0x149

op_linearsearch = 0x150
op_binarysearch = 0x151
op_linkedsearch = 0x152

op_callf = 0x160
op_callfi = 0x161
op_callfii = 0x162
op_callfiii  = 0x163

op_mzero = 0x170
op_mcopy = 0x171
op_malloc = 0x178
op_mfree = 0x179

#	Operands lists
list_none = (0, 4, None)
list_S = (1, 4, (2, ))
list_LS = (2, 4, (1, 2))
list_LLS = (3, 4, (1, 1, 2))
list_LLLS = (4, 4, (1, 1, 1, 2))
list_LLLLS = (5, 4, (1, 1, 1, 1, 2))
list_LLLLLS = (6, 4, (1, 1, 1, 1, 1, 2))
list_LLLLLLS = (7, 4, (1, 1, 1, 1, 1, 1, 2))
list_LLLLLLLS = (8, 4, (1, 1, 1, 1, 1, 1, 1, 2))
list_L = (1, 4, (1, ))
list_LL = (2, 4, (1, 1))
list_LLL = (3, 4, (1, 1, 1))
list_2LS = (2, 2, (1, 2))
list_1LS = (2, 1, (1, 2))
list_SL = (2, 4, (2, 1))
list_SS = (2, 4, (2, 2))

def lookupOperands(vm, opcode):
	"Look up the operands structure for a particular opcode"
	if opcode == op_nop:
		return list_none

	if opcode in (op_add, op_sub, op_mul, op_div, op_mod, op_bitand, op_bitor, op_bitxor, op_shiftl, op_sshiftr, op_ushiftr):
		return list_LLS

	if opcode in (op_neg, op_bitnot):
		return list_LS

	if opcode in (op_jump, op_jumpabs):
		return list_L
	if opcode in (op_jz, op_jnz):
		return list_LL
	if opcode in (op_jeq, op_jne, op_jlt, op_jge, op_jgt, op_jle, op_jltu, op_jgeu, op_jgtu, op_jleu):
		return list_LLL

	if opcode == op_call:
		return list_LLS
	if opcode == op_return:
		return list_L
	if opcode == op_catch:
		return list_SL
	if opcode in (op_throw, op_tailcall):
		return list_LL

	if opcode in (op_sexb, op_sexs):
		return list_LS
	if opcode == op_copy:
		return list_LS
	if opcode == op_copys:
		return list_2LS
	if opcode == op_copyb:
		return list_1LS
	if opcode in (op_aload, op_aloads, op_aloadb, op_aloadbit):
		return list_LLS
	if opcode in (op_astore, op_astores, op_astoreb, op_astorebit):
		return list_LLL

	if opcode == op_stkcount:
		return list_S
	if opcode == op_stkpeek:
		return list_LS
	if opcode == op_stkswap:
		return list_none
	if opcode == op_stkroll:
		return list_LL
	if opcode == op_stkcopy:
		return list_L

	if opcode in (op_streamchar, op_streamunichar, op_streamnum, op_streamstr):
		return list_L
	if opcode == op_getstringtbl:
		return list_S
	if opcode == op_setstringtbl:
		return list_L
	if opcode == op_getiosys:
		return list_SS
	if opcode == op_setiosys:
		return list_LL

	if opcode == op_random:
		return list_LS
	if opcode == op_setrandom:
		return list_L

	if opcode == op_verify:
		return list_S
	if opcode == op_restart:
		return list_none
	if opcode in (op_save, op_restore):
		return list_LS
	if opcode in (op_saveundo, op_restoreundo):
		return list_S
	if opcode == op_protect:
		return list_LL

	if opcode == op_quit:
		return list_none

	if opcode == op_gestalt:
		return list_LLS

	if opcode == op_debugtrap:
		return list_L

	if opcode == op_getmemsize:
		return list_S
	if opcode == op_setmemsize:
		return list_LS

	if opcode in (op_linearsearch, op_binarysearch):
		return list_LLLLLLLS
	if opcode == op_linkedsearch:
		return list_LLLLLLS

	if opcode == op_glk:
		return list_LLS

	if opcode == op_callf:
		return list_LS
	if opcode == op_callf:
		return list_LLS
	if opcode == op_callfi:
		return list_LLLS
	if opcode == op_callfii:
		return list_LLLS
	if opcode == op_callfiii:
		return list_LLLLS

	if opcode == op_mzero:
		return list_LL
	if opcode == op_mcopy:
		return list_LLL
	if opcode == op_malloc:
		return list_LS
	if opcode == op_mfree:
		return list_L

	return None

def parseOperands(vm, operands):
	"Read the list of operands for an instruction and return them. Assumes the PC is at the beginning of the operand mode list. Upon return, PC will be at the next instruction"
	#	We'll need a few variables
	numops = operands[0]
	argsize = operands[1]
	modeaddr = vm.memory.curPos
	vm.memory.curPos += (numops + 1) / 2
	inst = [0, [0] * numops]

	for ix in range(numops):
		#	Get mode of current operand
		if (ix & 1) == 0:
			modeval = vm.memory.read('B', modeaddr, False)[0]
			mode = modeval & 0x0F
		else:
			mode = (modeval >> 4) & 0x0F
			modeaddr += 1

		if operands[2][ix] == 1:
			#	Load operands
			if mode == 8:
				#	Pop off the stack
				if (vm.stack.curPos < vm.registers['valstackbase'] + 4):
					raise GlulxError("Stack underflow in operand")
				vm.stack.curPos -= 4
				value = vm.stack.read('L', update = False)[0]
			elif mode == 0:
				#	Constant zero
				value = 0
			elif mode == 1:
				#	One-byte constant
				value = vm.memory.read('b')[0]
			elif mode == 2:
				#	Two-byte constant
				value = vm.memory.read('h')[0]
			elif mode == 3:
				#	Four-byte constant
				value = vm.memory.read('L')[0]
			elif mode == 15:
				#	Main memory RAM, four byte address
				addr = vm.memory.read('L')[0] + vm.headers['RAMSTART']
			elif mode == 14:
				#	Main memory RAM, two byte address
				addr = vm.memory.read('H')[0] + vm.headers['RAMSTART']
			elif mode == 13:
				#	Main memory RAM, one byte address
				addr = vm.memory.read('B')[0] + vm.headers['RAMSTART']
			elif mode == 7:
				#	Main memory, four byte address
				addr = vm.memory.read('L')[0]
			elif mode == 6:
				#	Main memory, two byte address
				addr = vm.memory.read('H')[0]
			elif mode == 5:
				#	Main memory, one byte address
				addr = vm.memory.read('B')[0]
			elif mode == 11:
				#	Locals, four byte address
				addr = vm.memory.read('L')[0]
			elif mode == 10:
				#	Locals, two byte address
				addr = vm.memory.read('H')[0]
			elif mode == 9:
				#	Locals, one byte address
				addr = vm.memory.read('B')[0]
			else:
				raise GlulxError("Unknown addressing mode in load operand")

			if mode in (5, 6, 7, 13, 14, 15):
				#	Get value from main memory
				if argsize == 4:
					value = vm.memory.read('L', addr, False)[0]
				elif argsize == 2:
					value = vm.memory.read('H', addr, False)[0]
				else:
					value = vm.memory.read('B', addr, False)[0]

			if mode in (9, 10, 11):
				#	Get value from the locals
				addr += vm.registers['localsbase']
				if argsize == 4:
					value = vm.stack.read('L', addr, False)[0]
				elif argsize == 2:
					value = vm.stack.read('H', addr, False)[0]
				else:
					value = vm.stack.read('B', addr, False)[0]

			inst[1][ix] = value

		if operands[2][ix] == 2:
			#	Store operands
			if mode == 0:
				#	Discard value
				inst[0] = 0
				inst[1][ix] = 0
			elif mode == 8:
				#	Push onto stack
				inst[0] = 3
				inst[1][ix] = 0
			elif mode == 15:
				#	Main memory RAM, four byte address
				addr = vm.memory.read('L')[0] + vm.headers['RAMSTART']
			elif mode == 14:
				#	Main memory RAM, two byte address
				addr = vm.memory.read('H')[0] + vm.headers['RAMSTART']
			elif mode == 13:
				#	Main memory RAM, one byte address
				addr = vm.memory.read('B')[0] + vm.headers['RAMSTART']
			elif mode == 7:
				#	Main memory, four byte address
				addr = vm.memory.read('L')[0]
			elif mode == 6:
				#	Main memory, two byte address
				addr = vm.memory.read('H')[0]
			elif mode == 5:
				#	Main memory, one byte address
				addr = vm.memory.read('B')[0]
			elif mode == 11:
				#	Locals, four byte address
				addr = vm.memory.read('L')[0]
			elif mode == 10:
				#	Locals, two byte address
				addr = vm.memory.read('H')[0]
			elif mode == 9:
				#	Locals, one byte address
				addr = vm.memory.read('B')[0]
			elif mode in (1, 2, 3):
				raise GlulxError("Constant addressing mode in store operand")
			else:
				raise GlulxError("Unknown addressing mode in store operand")

			if mode in (5, 6, 7, 13, 14, 15):
				#	Writing to the main memory
				inst[0] = 1
				inst[1][ix] = addr

			if mode in (9, 10, 11):
				#	Store to locals
				inst[0] = 2
				inst[1][ix] = addr

	return inst

def storeOperand(vm, desttype, destaddr, value):
	"Store a result value"
	if desttype == 0:
		#	Do nothing, discard value
		pass
	elif desttype == 1:
		#	Main memory
		vm.memory.write('L', (value, ), destaddr, False)
	elif desttype == 2:
		#	Locals
		vm.stack.write('L', (value, ), destaddr + vm.registers['localsbase'], False)
		print "destaddr %s" % (destaddr + vm.registers['localsbase'])
	elif desttype == 3:
		#	Push onto stack
		if vm.stack.curPos + 4 > vm.headers['STACKSIZE']:
			raise GlulxError("Stack overflow in store operand")
		vm.stack.write('L', (value, ))
		print "stackptr %s" % vm.stack.curPos
	else:
		raise GlulxError("Unknown destination type in store operand")
